package org.fhm.zdte.client.service.proxy;

import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelHandler;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
import io.netty.handler.codec.http.*;
import io.netty.handler.codec.http.websocketx.TextWebSocketFrame;
import io.netty.util.ReferenceCountUtil;
import org.fhm.substrate.annotation.BeanInitial;
import org.fhm.substrate.annotation.Component;
import org.fhm.substrate.annotation.Setup;
import org.fhm.substrate.manager.Bootstrap;
import org.fhm.zdte.client.config.ProxyConfiguration;
import org.fhm.zdte.client.exception.ClientException;

import java.io.BufferedReader;
import java.io.File;
import java.io.FileReader;
import java.io.IOException;
import java.util.Objects;

/**
 * It is a proxy page entrance handler that is inherited {@link ChannelInboundHandlerAdapter}.
 *
 * @author 谭波
 * @since 2023/12/29
 */
@Component
@ChannelHandler.Sharable
public class PageEntranceHandler extends ChannelInboundHandlerAdapter {

    @Setup
    private ProxyConfiguration proxyConfiguration;

    private String pagePath;

    /**
     * Encapsulate page.
     *
     * @param br   The page file stream of {@link BufferedReader}.
     * @param path The page file path.
     * @return Full http response object.
     * @throws IOException was thrown when an error occurs on the during encapsulated page.
     */
    private static FullHttpResponse encapsulatePage(BufferedReader br, String path) throws IOException {
        StringBuilder html = new StringBuilder();
        String text;
        while (Objects.nonNull((text = br.readLine()))) {
            html.append(text).append(System.lineSeparator());
        }
        FullHttpResponse response = new DefaultFullHttpResponse(
                HttpVersion.HTTP_1_1, HttpResponseStatus.OK,
                Unpooled.wrappedBuffer(html.toString().getBytes(Bootstrap.charset))
        );
        response.headers().set(HttpHeaderNames.CONTENT_TYPE, "text/"
                + (path.endsWith(".js") ? "javascript" : path.substring(path.lastIndexOf(".") + 1))
                + "; charset=UTF-8");
        response.headers().set(HttpHeaderNames.CONTENT_LENGTH, response.content().readableBytes());
        return response;
    }

    /**
     * Trigger a read event.
     *
     * @param ch  Channel handler.
     * @param msg Message object.
     */
    @Override
    public void channelRead(ChannelHandlerContext ch, Object msg) {
        if (msg instanceof TextWebSocketFrame) {
            ch.fireChannelRead(msg);
            return;
        }
        if (!(msg instanceof FullHttpRequest))
            return;
        FullHttpRequest request = (FullHttpRequest) msg;
        if ("websocket".equals(request.headers().get("Upgrade"))) {
            ch.fireChannelRead(msg);
            return;
        }
        String uri = request.uri();
        ReferenceCountUtil.release(msg);
        returnStaticResource(ch, uri);
    }

    /**
     * Response static resource.
     *
     * @param ch  Channel handler.
     * @param uri URL path
     */
    private void returnStaticResource(ChannelHandlerContext ch, String uri) {
        String pagePath = proxyConfiguration.getPagePath();
        if (Objects.isNull(pagePath) || pagePath.isEmpty()) {
            responseFail(ch, "the file path of the configured portal page is incorrect");
        } else {
            String fullPath = pagePath + uri;
            File file = new File(fullPath);
            if (!file.exists())
                responseFail(ch, "the file " + fullPath + " is not exist");
            if (!file.isFile())
                responseFail(ch, "the file " + fullPath + " is not a file");
            try (
                    FileReader fr = new FileReader(file);
                    BufferedReader br = new BufferedReader(fr)
            ) {
                ch.writeAndFlush(encapsulatePage(br, fullPath));
            } catch (IOException e) {
                responseFail(ch, "failed to read the file " + fullPath);
            }
        }
    }

    /**
     * Responding to failed page.
     *
     * @param ch  Channel handler.
     * @param tip Error tip.
     */
    private void responseFail(ChannelHandlerContext ch, String tip) {
        tip = "<h3 style=\"text-align: center\">Zero Distance</h3>\n" +
                "<div style=\"text-align: center;color: red\">" + tip + "</div>";
        FullHttpResponse response = new DefaultFullHttpResponse(HttpVersion.HTTP_1_1, HttpResponseStatus.NOT_FOUND, Unpooled.wrappedBuffer(tip.getBytes(Bootstrap.charset)));
        response.headers().set(HttpHeaderNames.CONTENT_TYPE, "text/html");
        response.headers().set(HttpHeaderNames.CONTENT_LENGTH, response.content().readableBytes());
        ch.writeAndFlush(response);
    }

    @BeanInitial
    private void beanInitial() throws ClientException {
        this.pagePath = proxyConfiguration.getPagePath();
        if (Objects.isNull(pagePath) || pagePath.isEmpty())
            return;
        File file = new File(pagePath);
        if (!file.exists()) {
            throw new ClientException("if the configured entrance page file storage path does not exist, check it and try again");
        } else {
            if (!file.isDirectory()) {
                throw new ClientException("the configured entrance page path is not a directory, please check it and try again");
            } else {
                if (!pagePath.substring(pagePath.length() - 1).equals(File.separator)) {
                    this.pagePath += File.separator;
                }
            }
        }
    }

}